/****************************************************** 
 *  Copyright 2018 IBM Corporation 
 *  Licensed under the Apache License, Version 2.0 (the "License"); 
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at 
 *  http://www.apache.org/licenses/LICENSE-2.0 
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 *  See the License for the specific language governing permissions and 
 *  limitations under the License.
 */

package com.tyqx.hashchainutil.org.app.util;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.bind.DatatypeConverter;

import com.tyqx.hashchainutil.org.app.config.Config;
import com.tyqx.hashchainutil.org.app.user.CAEnrollment;
import com.tyqx.hashchainutil.org.app.user.UserContext;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.util.io.pem.PemGenerationException;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemObjectGenerator;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.security.CryptoPrimitives;

/**
 * @author Balaji Kadambi
 */

public class Util {

    /**
     * Serialize user
     *
     * @param userContext
     * @throws Exception
     */
    public static void writeUserContext(UserContext userContext) throws Exception {
        File dir = Paths.get("users" ,userContext.getAffiliation(), userContext.getName()).toFile();
        File file = Paths.get("users" ,userContext.getAffiliation(), userContext.getName(),  "msp.ser").toFile();

        if (!dir.exists()) {
            dir.mkdirs();
        }

        FileOutputStream fileOutputStream = new FileOutputStream(file);
        ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);

        // Method for serialization of object
        out.writeObject(userContext);

        out.close();
        fileOutputStream.close();
    }

    public static void writeUserContextTLS(UserContext userContext) throws Exception {
        File dir = Paths.get("users" ,userContext.getAffiliation(), userContext.getName()).toFile();
        File file = Paths.get("users" ,userContext.getAffiliation(), userContext.getName(),  "tls.ser").toFile();
       /* String directoryPath = "users/" + userContext.getAffiliation();
        String filePath = directoryPath + "/" + userContext.getName() + "TLS.ser";
        File directory = new File(directoryPath);*/
        if (!dir.exists())
            dir.mkdirs();

        FileOutputStream fileOutputStream = new FileOutputStream(file);
        ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);

        // Method for serialization of object
        out.writeObject(userContext);

        out.close();
        fileOutputStream.close();
    }

    /**
     * Deserialize user
     *
     * @param affiliation
     * @param username
     * @return
     * @throws Exception
     */
    public static UserContext readUserContext(String affiliation, String username) throws Exception {
//        String filePath = "users/" + affiliation + "/" + username + ".ser";
        File file = Paths.get("users" ,affiliation, username,  "msp.ser").toFile();
        if (file.exists()) {
            // Reading the object from a file
            FileInputStream fileStream = new FileInputStream(file);
            ObjectInputStream in = new ObjectInputStream(fileStream);
            UserContext uContext = (UserContext) in.readObject();

            in.close();
            fileStream.close();
            return uContext;
        }
        return null;
    }

    public static Map<String,Object> readUserContext(String caType, String userName, Long orgIndex) throws Exception {
//        String filePath = "users/" + affiliation + "/" + username + ".ser";
        Map<String,Object> pathMap=new HashMap<>();
        String orgName = "org" + orgIndex.toString() + ".hashchain.com";
        Path caPath = Paths.get(Config.HOST_WORKING_DIR, "dynamicChain","cryptos", "fabric-ca-client", "crypto-config", "peerOrganizations", orgName, "users", userName + "@" + orgName, caType, "ca.crt");
        Path clientCertPath = Paths.get(Config.HOST_WORKING_DIR, "dynamicChain","cryptos", "fabric-ca-client", "crypto-config", "peerOrganizations", orgName, "users", userName + "@" + orgName, caType, "client.crt");
        Path clientKeyPath = Paths.get(Config.HOST_WORKING_DIR, "dynamicChain","cryptos", "fabric-ca-client", "crypto-config", "peerOrganizations", orgName, "users", userName + "@" + orgName, caType, "client.key");


        File caFile = caPath.toFile();

        File clientCertFile = clientCertPath.toFile();

        File clientKeyFile = clientKeyPath.toFile();

        if (caFile.exists()) {
            // Reading the object from a file
            pathMap.put("caPath",caPath.toAbsolutePath());
        }
        if (clientCertFile.exists()) {
            // Reading the object from a file
            pathMap.put("clientCertPath",clientCertPath.toAbsolutePath());
        }
        if (clientKeyFile.exists()) {
            // Reading the object from a file
            pathMap.put("clientKeyPath",clientKeyPath.toAbsolutePath());
        }
        return pathMap;
    }


    public static UserContext readUserContextTLS(String affiliation, String username) throws Exception {
        File fileTLS = Paths.get("users" ,affiliation, username,  "tls.ser").toFile();
        if (fileTLS.exists()) {

            FileInputStream fileStreamTLS = new FileInputStream(fileTLS);
            ObjectInputStream inTLS = new ObjectInputStream(fileStreamTLS);
            UserContext uContextTLS = (UserContext) inTLS.readObject();

            inTLS.close();
            fileStreamTLS.close();
            return uContextTLS;
        }

        return null;
    }


    /**
     * Create enrollment from key and certificate files.
     *
     * @param
     * @param keyFileName
     * @param certFileName
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws CryptoException
     */
    public static CAEnrollment getEnrollment(String keyFolderPath, String keyFileName, String certFolderPath, String certFileName)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, CryptoException {
        PrivateKey key = null;
        String certificate = null;
        InputStream isKey = null;
        BufferedReader brKey = null;

        try {

            isKey = new FileInputStream(keyFolderPath + File.separator + keyFileName);
            brKey = new BufferedReader(new InputStreamReader(isKey));
            StringBuilder keyBuilder = new StringBuilder();

            for (String line = brKey.readLine(); line != null; line = brKey.readLine()) {
                if (line.indexOf("PRIVATE") == -1) {
                    keyBuilder.append(line);
                }
            }

            certificate = new String(Files.readAllBytes(Paths.get(certFolderPath, certFileName)));

            byte[] encoded = DatatypeConverter.parseBase64Binary(keyBuilder.toString());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
//            KeyFactory kf = KeyFactory.getInstance("ECDSA");
            KeyFactory kf = KeyFactory.getInstance("EC");
            key = kf.generatePrivate(keySpec);
        } finally {
            isKey.close();
            brKey.close();
        }

        CAEnrollment enrollment = new CAEnrollment(key, certificate);
        return enrollment;
    }

    public static void cleanUp() {
        String directoryPath = "users";
        File directory = new File(directoryPath);
        deleteDirectory(directory);
    }

    public static boolean deleteDirectory(File dir) {
        if (dir.isDirectory()) {
            File[] children = dir.listFiles();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDirectory(children[i]);
                if (!success) {
                    return false;
                }
            }
        }

        // either file or an empty directory
        Logger.getLogger(Util.class.getName()).log(Level.INFO, "Deleting - " + dir.getName());
        return dir.delete();
    }

    public static Properties setTlsProperties(String tlsFilePath) throws Exception {
        File tlsFile = new File(tlsFilePath);
        String certficate = new String(IOUtils.toByteArray(new FileInputStream(tlsFile)), "UTF-8");
        Properties properties = new Properties();
        properties.put("pemBytes", certficate.getBytes());
        properties.setProperty("pemFile", tlsFile.getAbsolutePath());
        properties.setProperty("allowAllHostNames", "true");
        return properties;
    }

    public static Properties gete2ePro(String type, String name) throws Exception {
        String orgName = name.substring(name.indexOf(".") + 1);
        Properties pro = new Properties();

        String username = "Admin";
//        if ("peer".equals(type)){
//            username = "User1";
//        }

        File cert = Paths.get("fabric-ca-client","crypto-config",
                type + "Organizations", orgName, type + "s", name, "tls", "server.crt").toFile();

        /*File cert = null;

        if ("orderer".equals(type)) {
            cert = Paths.get("fabric-ca-client","crypto-config",
                    type + "Organizations", orgName, type + "s", name, "tls", "server.crt").toFile();
        } else if ("peer".equals(type)) {
            cert = Paths.get("fabric-ca-client", "crypto-config",
                    type + "Organizations", orgName, "users", username + "@" + orgName, "tls", "admincerts", "cert.pem").toFile();
        }*/

        File clientCert = Paths.get("fabric-ca-client","crypto-config",
                type + "Organizations", orgName, "users", username + "@" + orgName, "tls", "client.crt").toFile();

        File clientKey = Paths.get("fabric-ca-client","crypto-config",
                type + "Organizations", orgName, "users", username + "@" + orgName, "tls", "client.key").toFile();

        if (!cert.exists()) {
            throw new RuntimeException(String.format("Missing cert file for : %s.Could not find at location : %s", name,
                    cert.getAbsolutePath()));
        }

        if (!clientCert.exists()) {
            throw new RuntimeException(String.format("Missing clientCert file for : %s.Could not find at location : %s", name,
                    clientCert.getAbsolutePath()));
        }

        if (!clientKey.exists()) {
            throw new RuntimeException(String.format("Missing clientKey file for : %s.Could not find at location : %s", name,
                    clientKey.getAbsolutePath()));
        }
        String certString = new String(IOUtils.toByteArray(new FileInputStream(cert)), "UTF-8");
        String clientCertString = new String(IOUtils.toByteArray(new FileInputStream(clientCert)), "UTF-8");
        String clientKeyString = new String(IOUtils.toByteArray(new FileInputStream(clientKey)), "UTF-8");

        CryptoPrimitives suite = new CryptoPrimitives();
        PrivateKey privateKey = suite.bytesToPrivateKey(clientKeyString.getBytes());

        pro.setProperty("pemFile", cert.getAbsolutePath());
        /*pro.setProperty("clientCertFile", clientCert.getAbsolutePath());
        pro.setProperty("clientKeyFile", clientKey.getAbsolutePath());*/

        /*pro.put("pemBytes", certString.getBytes());
        pro.put("clientCertBytes", clientCertString.getBytes());
        pro.put("clientKeyBytes", clientKeyString.getBytes());*/

        pro.setProperty("hostnameOverride", name);
//        pro.setProperty("hostnameOverride", "localhost");
        pro.setProperty("trustServerCertificate", "true");
        pro.setProperty("sslProvider", "openSSL");
        pro.setProperty("negotiationType", "TLS");
        pro.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);
        return pro;
    }


}
